"""
XML-based test runner for GCR Solver Manager.
This runner passes XML configuration files directly to the solver binary.
"""

import os
import subprocess
import logging
from pathlib import Path
from typing import Dict, Any, Optional, Tuple, List
from datetime import datetime

from ..logs.manager import LogManager
from ..config.xml_config import SolverConfig, XMLConfigParser

logger = logging.getLogger(__name__)


class XMLTestRunner:
    """Test runner that uses XML configuration files."""
    
    def __init__(self, source_dir: Optional[str] = None, log_manager: Optional[LogManager] = None):
        """Initialize XML test runner."""
        self.source_dir = Path(source_dir or '../').resolve()
        self.log_manager = log_manager or LogManager()
        # Build manager removed - using binary execution only
        
        if not self.source_dir.exists():
            raise FileNotFoundError(f"Source directory not found: {self.source_dir}")
    
    def run_with_xml_config(self, config_file: str, overrides: Optional[Dict[str, str]] = None,
                           tags: Optional[List[str]] = None, force_rebuild: bool = False) -> Tuple[bool, int]:
        """
        Run solver with XML configuration file.
        
        Args:
            config_file: Path to XML configuration file
            overrides: Parameter overrides (Section.param -> value)
            tags: Tags for this test run
            force_rebuild: Force rebuild even if not needed
            
        Returns:
            Tuple of (success, run_id)
        """
        overrides = overrides or {}
        tags = tags or []
        
        config_path = Path(config_file)
        if not config_path.is_absolute():
            config_path = self.source_dir / config_path
        
        if not config_path.exists():
            logger.error(f"Configuration file not found: {config_path}")
            return False, 0
        
        # Load and parse XML configuration
        parser = XMLConfigParser()
        if not parser.load_from_file(config_path):
            logger.error(f"Failed to load XML configuration: {config_path}")
            return False, 0
        
        config = parser.parse()
        
        # Extract key configuration values
        gpu_backend = config.get_sublist("System").get_string("gpu_backend", "none")
        solver_type = config.get_sublist("Solver").get_string("type", "gcr")
        use_debug = config.get_sublist("System").get_bool("use_debug", False)
        total_processes = config.get_sublist("MPI").get_int("total_processes", 4)
        
        logger.info(f"Running XML test: {config_file}")
        logger.info(f"  Solver: {solver_type}, GPU: {gpu_backend}, Debug: {use_debug}")
        
        # Ensure appropriate build exists
        build_success, build_run_id = self._ensure_xml_build(gpu_backend, use_debug, force_rebuild)
        if not build_success:
            logger.error("Build failed - cannot run test")
            return False, build_run_id or 0
        
        # Execute test with XML configuration
        success, test_output = self._execute_xml_test(config_path, total_processes, overrides)
        
        # Create test record
        test_config = {
            'config_file': str(config_path),
            'solver_type': solver_type,
            'gpu_backend': gpu_backend,
            'use_debug': use_debug,
            'total_processes': total_processes,
            'overrides': overrides,
            'xml_based': True,
            'timestamp': datetime.now().isoformat()
        }
        
        # Store test results
        try:
            log_content = f"XML Configuration Test\nConfig: {config_path}\nOverrides: {overrides}\n\nOutput:\n{test_output}"
            run_id = self.log_manager.store_test_log(solver_type, gpu_backend, test_config, 
                                                   log_content, tags + ["xml-config"])
            
            if success:
                logger.info(f"XML test completed successfully (run_id: {run_id})")
            else:
                logger.error(f"XML test failed (run_id: {run_id})")
            
            return success, run_id
            
        except Exception as e:
            logger.error(f"Failed to store test results: {e}")
            return False, 0
    
    def _ensure_xml_build(self, gpu_backend: str, use_debug: bool, force_rebuild: bool) -> Tuple[bool, int]:
        """Ensure XML-enabled build exists."""
        build_options = {
            'xml_config': True,  # Enable XML configuration support
            'debug': use_debug
        }
        
        logger.info(f"Ensuring XML build for {gpu_backend} (debug={use_debug})")
        return self.build_manager.ensure_build(gpu_backend, build_options, force_rebuild)
    
    def _execute_xml_test(self, config_path: Path, total_processes: int, 
                         overrides: Dict[str, str]) -> Tuple[bool, str]:
        """Execute solver test with XML configuration."""
        try:
            # Determine binary name
            binary_name = self._get_xml_binary_name(config_path)
            binary_path = self.source_dir / "build" / binary_name
            
            if not binary_path.exists():
                return False, f"XML-enabled binary not found: {binary_path}"
            
            # Build command
            cmd = [
                "mpirun",
                f"-np", str(total_processes),
                str(binary_path),
                f"--config={config_path}"
            ]
            
            # Add override parameters
            for key, value in overrides.items():
                if '.' in key:
                    section, param = key.split('.', 1)
                    # Convert common overrides to command line format
                    if section == "Solver" and param == "max_iterations":
                        cmd.extend([f"--maxit={value}"])
                    elif section == "Solver" and param == "type":
                        cmd.extend([f"--solver={value}"])
                    elif section == "Problem" and param == "resolution":
                        cmd.extend([f"--resolution={value}"])
                    elif section == "System" and param == "gpu_backend":
                        cmd.extend([f"--gpu={value}"])
                    else:
                        logger.warning(f"Override {key}={value} not supported via command line")
            
            logger.info(f"Executing: {' '.join(cmd)}")
            
            # Execute the solver
            result = subprocess.run(cmd, cwd=self.source_dir, capture_output=True, 
                                  text=True, timeout=1800)
            
            success = result.returncode == 0
            output = (f"Command: {' '.join(cmd)}\nReturn code: {result.returncode}\n\n"
                     f"STDOUT:\n{result.stdout}\n\nSTDERR:\n{result.stderr}")
            
            return success, output
            
        except subprocess.TimeoutExpired:
            return False, "XML test execution timed out after 30 minutes"
        except Exception as e:
            return False, f"XML test execution failed: {e}"
    
    def _get_xml_binary_name(self, config_path: Path) -> str:
        """Get binary name for XML-enabled solver."""
        # Parse config to determine binary requirements
        parser = XMLConfigParser()
        if parser.load_from_file(config_path):
            config = parser.parse()
            gpu_backend = config.get_sublist("System").get_string("gpu_backend", "none")
            use_debug = config.get_sublist("System").get_bool("use_debug", False)
            
            name = "main-xml"
            
            if gpu_backend == "cuda":
                name += "-cuda"
            elif gpu_backend == "hip":
                name += "-hip"
            
            if use_debug:
                name += "-debug"
            
            return f"{name}.exe"
        
        return "main-xml.exe"  # Fallback
    
    def create_config_from_params(self, solver: str, gpu: Optional[str] = None, 
                                 params: Optional[Dict[str, Any]] = None,
                                 config_name: str = "temp_config.xml") -> Path:
        """
        Create temporary XML configuration from parameters.
        
        Args:
            solver: Solver type
            gpu: GPU backend
            params: Additional parameters
            config_name: Name for temporary config file
            
        Returns:
            Path to created configuration file
        """
        params = params or {}
        
        # Create configuration dictionary
        config_dict = {
            "System": {
                "gpu_backend": gpu or "none",
                "communication": "nccl" if gpu else "mpi",
                "use_debug": params.get('debug', False),
                "use_random_data": params.get('random_data', True),
            },
            "MPI": {
                "total_processes": params.get('nproc', 4),
                "nprocx": params.get('nprocx', 2),
                "nprocy": params.get('nproc', 4) // params.get('nprocx', 2),
            },
            "Problem": {
                "resolution": params.get('resolution', 1.0),
                "data_source": "random",
                "netcdf_file": "",
            },
            "Solver": {
                "type": solver,
                "max_iterations": params.get('maxit', 20),
                "convergence_tol": 1.0e-6,
                "verbosity": 2 if params.get('debug', False) else 1,
            },
            "IO": {
                "export_arrays": params.get('debug', False),
                "log_level": "debug" if params.get('debug', False) else "info",
            },
        }
        
        # Create SolverConfig instance
        solver_config = SolverConfig()
        solver_config.initialize_from_dict(config_dict)
        
        # Export to XML file
        config_path = self.source_dir / "config" / config_name
        config_path.parent.mkdir(exist_ok=True)
        solver_config.export_config(config_path)
        
        logger.info(f"Created temporary configuration: {config_path}")
        return config_path
    
    def list_available_configs(self) -> List[str]:
        """List available XML configuration files."""
        config_dir = self.source_dir / "config"
        if not config_dir.exists():
            return []
        
        configs = []
        for xml_file in config_dir.glob("**/*.xml"):
            configs.append(str(xml_file.relative_to(self.source_dir)))
        
        return sorted(configs)
    
    def validate_config(self, config_file: str) -> Tuple[bool, List[str]]:
        """
        Validate XML configuration file.
        
        Args:
            config_file: Path to configuration file
            
        Returns:
            Tuple of (is_valid, error_messages)
        """
        config_path = Path(config_file)
        if not config_path.is_absolute():
            config_path = self.source_dir / config_path
        
        if not config_path.exists():
            return False, [f"Configuration file not found: {config_path}"]
        
        # Parse configuration
        parser = XMLConfigParser()
        if not parser.load_from_file(config_path):
            return False, [f"Failed to parse XML: {parser.get_last_error()}"]
        
        # Validate using SolverConfig
        solver_config = SolverConfig()
        solver_config.config = parser.parse()
        solver_config.initialized = True
        
        is_valid = solver_config.validate_config()
        errors = solver_config.get_validation_errors()
        
        return is_valid, errors